#!/bin/sh

## Updater für die user.js von Privacy-Handbuch.de
## Version: 1.6.2
## by: TotallyLeGIT

## based on the ghacks user.js updater by:
## Author: Pat Johnson (@overdodactyl)
## Additional contributors: @earthlng, @ema-pe, @claustromaniac

## Dependencies: ·curl or wget


#########################
# Misc/Helper Functions #
#########################

# Set variables based on the operating system reported by uname.
abstract_os() {
	if [ "$(uname)" = 'Darwin' ]; then
		readonly open_command='open'
		readonly ff_dir="${HOME}/Library/Application Support/Firefox"
		readonly ff_file_path="${ff_dir}/profiles.ini"
		readonly global_override_path="${global_override_path:-${ff_dir}/global-user-overrides.js}"
		readonly tb_file_path="${HOME}/Library/Thunderbird/profiles.ini"
		readonly ff_exec_dir='/Applications/Firefox.app/Contents/MacOS/'
		readonly sed_compat_os_script='s/Resources$/MacOS/'
	else
		readonly open_command='xdg-open'
		readonly ff_dir="${HOME}/.mozilla/firefox"
		readonly ff_file_path="${ff_dir}/profiles.ini"
		readonly global_override_path="${global_override_path:-${ff_dir}/global-user-overrides.js}"
		readonly tb_file_path="${HOME}/.thunderbird/profiles.ini"
		readonly ff_exec_dir=''
		readonly sed_compat_os_script=''
	fi
	# set effective path for each profile.ini
	[ -f "${ff_file_path}" ] && ff_file="${ff_file_path}" || ff_file=''
	[ -f "${tb_file_path}" ] && tb_file="${tb_file_path}" || tb_file=''
	readonly ff_file tb_file
}

err_exit() {
	printv '3' '%s\n' "${color_err}[Error] ${*}${color_neutral_stderr}" 1>&2
	exit 1
}

err_help_exit() {
	printf '%s\n' "${color_err}[Error] ${*}${color_neutral_stderr}" 1>&2
	printf "Use '-h' option for more information.\n" 1>&2
	exit 1
}

print_global_overrides() {
	[ ! -e "${global_override_path}" ] && return 0
	printv_info '0' 'global overrides' "${global_override_path}"
}

print_warn() {
	printv '2' '%s\n' "${color_warn}[Warning] ${*}${color_neutral_stderr}" 1>&2
}

# Print if message is more important than the quiescence specified.
# 	$1: importance (should be a non-negative integer)
# 	$2: printf format string
# 	$3: string to print (only one)
printv() {
	# shellcheck disable=SC2059
	[ "${1}" -ge "${quiescence}" ] && printf "${2}" "${3}" || return 0
}

# Format and print informational text to terminal.
# 	$1 : importance (should be a non-negative integer)
# 	$2 : information label
# 	$3…: information (all arguments >= 3)
printv_info() {
	importance="${1}"
	shift
	printv "${importance}" '%-19s' "${1}:"
	shift
	printv "${importance}" '%s\n' "${color_info}${*}${no_color_stdout}"
}

# See 'extract_version', but without Thunderbird.
extract_ff_version() {
	if ! ff_version_output="$("${1}"firefox --version 2>/dev/null)"; then
		return 1
	elif printf '%s' "${ff_version_output}" | grep -q 'esr'; then
		ff_version_extracted="Firefox ESR $(printf '%s' "${ff_version_output}" \
													| cut -d ' ' -f 3 | cut -d '.' -f 1)"
	else
		ff_version_extracted='Firefox release'
	fi
	printf '%s' "${ff_version_extracted}"
}

# Determine if a given directory contains Thunderbird or Firefox.
# Print 'Firefox ESR XY' (XY being the major version number), 'Firefox release'
# or 'Thunderbird'.  Return 1 if none found.
# 	$1: directory of executable (CAUTION: append a trailing / )
# 			If no argument is given, executables are searched for in $PATH.
extract_version() {
	if [ -f "${1}thunderbird" ] && [ -x "${1}thunderbird" ]; then
		printf 'Thunderbird'
		return 0
	else
		extract_ff_version "${1}"
	fi
}

# Parse command line options and set variables accordingly.
parse_options() {
	if [ "${#}" -ne 0 ]; then
		# display usage if first argument contains -help
		if printf '%s' "${1}" | grep -qi '\-help'; then
			usage
			exit 0
		else
			while getopts ':abc:de:g:hilno:p:qrstuvy' opt; do
				case "${opt}" in
					a)
						profile_path_option='all'
						;;
					b)
						backup_multiple='false'
						;;
					c)
						if ! config_wanted_global="$(printf '%s' "${OPTARG}" \
							| tr '[:upper:]' '[:lower:]' \
							| awk '{print $1; exit}' \
							| grep '^arkenfox$\|^minimal$\|^moderat$\|^medium$\|^streng$\|^hotspot$\|^empty$\|^horlogeskynet$\|^tb$\|^tb-streng$')"
						then
							err_help_exit "Invalid CONFIG: '${OPTARG}'"
						fi
						;;
					d)
						compare='true'
						;;
					e)
						if ff_version_wanted_global="$(printf '%s' "${OPTARG}" \
																		| awk '{print $1; exit}' \
																		| grep '^[[:digit:]]*$')"
						then
							ff_version_specified='true'
						else
							err_help_exit 'VERSION must be an integer'
						fi
						;;
					g)
						[ ! -e "${OPTARG}" ] && err_exit "'${OPTARG}': not found"
						global_override_path="${OPTARG}"
						;;
					h)
						usage
						exit 0
						;;
					i)
						view='true'
						;;
					l)
						profile_path_option='list'
						;;
					n)
						override='false'
						;;
					o)
						override='true'
						override_path="${OPTARG}"
						;;
					p)
						update_profile_path "${OPTARG}"
						[ "${profile_path_option}" = 'false' ] && profile_path_option='true'
						;;
					q)
						quiescence="$((quiescence+1))"
						;;
					r)
						just_read='true'
						;;
					# TODO: remove '-s' in future version
					s|u)
						update='true'
						;;
					t)
						confirm='no'
						dry_run='true'
						;;
					v)
						printf '%s\n' "PH-userjs-updater ${version}"
						exit 0
						;;
					y)
						confirm='no'
						;;
					\?)
						err_help_exit "Invalid option: '${OPTARG}'"
						;;
					:)
						err_help_exit "Option requires an argument: '${OPTARG}'"
						;;
				esac
			done
		fi
	fi
}

# Initialize misc stuff.
# 	$1: this script's name (usually $0)
prepare() {
	tmp_dir="$(mktemp -dt userjs.XXXXXX)"
	readonly tmp_dir
	trap 'exit 130' INT HUP QUIT TERM ALRM
	# shellcheck disable=SC2154
	trap 'rc="${?}"; rm -rf "${tmp_dir}"; exit "${rc}"' EXIT

	script_path="$(readlink -f "${1}" 2>/dev/null \
		|| greadlink -f "${1}" 2>/dev/null || printf '%s' "${1}")"
	readonly script_path

	# Download method priority: curl > wget
	if command -v curl >/dev/null; then
		readonly download_command='curl --location --max-redirs 2 --max-time 30 --fail --silent --output'
	elif command -v wget >/dev/null; then
		# -O file = --output-document=file
		readonly download_command='wget --max-redirect=2 --timeout=30 --quiet -O'
	else
		err_exit 'This script requires curl or wget'
	fi
}

# Determine Firefox (FF) update channel and set $ff_version_wanted_global
# accordingly: major version number for FF esr (e. g. 91) or 0 for FF release
set_ff_version_wanted_global() {
	if [ "${ff_version_specified}" = 'false' ]; then
		if ! ff_version_wanted_global="$(extract_ff_version "${ff_exec_dir}")"; then
			ff_version_wanted_global='none'
		fi
	else
		if [ "${ff_version_wanted_global}" -eq 0 ]; then
			ff_version_wanted_global='Firefox release'
		else
			esr_to_dl="ff${ff_version_wanted_global}"
			ff_version_wanted_global="Firefox ESR ${ff_version_wanted_global}"
		fi
	fi
	readonly ff_version_wanted_global
	printv_info '0' 'default browser' "${ff_version_wanted_global}"
}

# Initialize variables needed for parse_options().
set_variables() {
	# Colors used for printing only when stdout is a terminal
	if [ -t 1 ]; then
		# green
		color_info="$(tput setaf 2)"
		# blue
		color_usage="$(tput setaf 4)"
		# blue, bold
		color_user_input="$(tput setaf 4)$(tput bold)"
		no_color_stdout="$(tput sgr0)"
	else
		color_info=''
		color_usage=''
		color_user_input=''
		no_color_stdout=''
	fi
	if [ -t 2 ]; then
		# red
		color_err="$(tput setaf 1)"
		# yellow (orange-ish)
		color_warn="$(tput setaf 3)"
		color_neutral_stderr="$(tput sgr0)"
	else
		color_err=''
		color_warn=''
		color_neutral_stderr=''
	fi
	readonly color_info color_usage color_user_input no_color_stdout
	readonly color_err color_warn color_neutral_stderr

	# Argument defaults
	backup_multiple='true'
	compare='false'
	compat_file='compatibility.ini'
	config_wanted_global='keep'
	confirm='yes'
	dry_run='false'
	ff_version_specified='false'
	just_read='false'
	# soft: look for user-overrides.js but only give info when none found
	override='soft'
	override_path='user-overrides.js'
	profile_path=''
	profile_path_option='false'
	pwd_start="$(pwd)"
	quiescence=0
	update='false'
	view='false'

	# constants
	readonly backup_dir='userjs_backups'
	readonly diff_dir='userjs_diffs'
	# TODO: remove version comment in line 4 in v2.0
	readonly version='1.6.2'
}

usage() {
	printf '%s' "${color_usage}userjs-updater${no_color_stdout}

Updater:             ${color_usage}https://notabug.org/TotallyLeGIT/PH-userjs-updater${no_color_stdout}
Firefox user.js:     ${color_usage}https://www.privacy-handbuch.de/handbuch_21u.htm
                     https://github.com/arkenfox/user.js${no_color_stdout}
Thunderbird user.js: ${color_usage}https://www.privacy-handbuch.de/handbuch_31p.htm
                     https://github.com/HorlogeSkynet/thunderbird-user.js${no_color_stdout}

${color_usage}Usage: ${0} [-abdghilnruvy] [-c CONFIG] [-e VERSION] [-o OVERRIDE] [-p PROFILE]${no_color_stdout}

  -a          Update all Firefox and Thunderbird profiles at once while keeping
              CONFIGs the same. Profiles without a user.js will be skipped.
  -b          Only keep one user.js backup and one diff file.
  -c CONFIG   Specify the Firefox user.js config you want: arkenfox, minimal, moderat, medium
              (= medium streng), streng (= sehr streng), hotspot or empty
              For Thunderbird: HorlogeSkynet, tb or tb-streng
              Empty creates a user.js containing just one commented line so that (global)
              overrides will be apllied to this profile.
              This option is only needed if you want to change to another config.
              Note that this option will always have effect on all selected profiles.
  -d          Create a diff file comparing old and new user.js within ${diff_dir} subdirectory.
  -e VERSION  Force download of user.js for Firefox ESR with version number VERSION for all
              profiles. Make sure the user.js for this version actually exists.
              Example: '-e 91' will download the user.js for Firefox ESR Version 91.x .
              Use '-e 0' to force the download of the user.js for the release update channel.
              Precedence to find Firefox update channel:
                -e > previous user.js > compatibility.ini > 'firefox --version'
  -g OVERRIDE Path to global overrides. These overrides get appended to every Firefox user.js, but
              not to unmanaged profiles (see '-c empty'). Per-profile overrides take precedence
              over these global ones. OVERRIDE may be a directory – see '-o'.
              The default path will always be applied, if present, even without this option:
              ~/.mozilla/firefox/global-user-overrides.js (Linux)
              ~/Library/Application Support/Firefox/global-user-overrides.js (MacOS)
  -h          Show this help message and exit.
  -i          Inspect the resulting user.js file.
  -l          Interactively choose your profile from a list.
  -n          Do not append any overrides even if user-overrides.js exists.
  -o OVERRIDE Filename or path to overrides file, needed if different than PROFILE/user-overrides.js .
              If used with -p, the path may be relative to PROFILE or an absolute path.
              If given a directory, all files inside ending in .js will be appended.
  -p PROFILE  Path to your profile directory, needed if different than this script's location.
              May be used multiple times.
              IMPORTANT: If the profile path includes spaces, wrap whole path in quotes.
  -q          Be quieter. Use multiple times to show less output: additional information,
              all information, warnings, errors will be silenced in this order. When completely
              silent, the outcome is still represented by the exit code.
  -r          Only download user.js to a temporary file and open it. This requires -c .
  -s          (Deprecated) This is an alias for -u .
  -t          Dry-run: Perform a trial run that doesn't change anything and produces mostly
              the same output. Implies -y .
  -u          Check for updates of this updater and install if available.
  -v          Display updater version and exit.
  -y          Update user.js without confirmation.
"
}

#########################
#  (Pro)File Handling   #
#########################

# Download a resource.
# 	$1: the file name, where the resource will be saved to
# 	$2: URL of the resource to download
# Return: 1 on error, else 0
download_file() {
	if [ "${2}" = 'empty' ]; then
		# This string makes parsing easy as it looks similar to a normal user.js.
		printf '// user_pref("_user.js.prhdb", "empty");\n' > "${1}" || return 1
	else
		${download_command} "${1}" "${2}" || return 1
	fi
}

# Update $profile_path (especially with list/-l) or exit on error.
get_profile_path() {
	if [ "${profile_path_option}" = 'list' ]; then
		if [ -n "${ff_file}" ]; then
			ini="${ff_file}"
			[ -n "${tb_file}" ] && ini='both' || printf 'Firefox detected.\n\n'
		elif [ -n "${tb_file}" ]; then
			ini="${tb_file}"
			printf 'Thunderbird detected.\n\n'
		else
			err_exit "Neither Thunderbird nor Firefox could be found, or '-l' is" \
					"not supported on your OS. Try '-p' option"
		fi

		if [ "${ini}" = 'both' ]; then
			while [ -z "${reply}" ]; do
				printf '%s' "${color_user_input}(F)irefox and (T)hunderbird were"
				printf '%s' " detected. Select one: f,t?${no_color_stdout} "
				read -r reply
			done
			reply="$(printf '%s' "${reply}" | tr '[:upper:]' '[:lower:]')"
			if [ -z "${reply##f*}" ]; then
				printf 'Firefox was selected.\n\n'
				read_ini_file "${ff_file}"
			elif [ -z "${reply##t*}" ]; then
				printf 'Thunderbird was selected.\n\n'
				read_ini_file "${tb_file}"
			else
				err_exit 'Could not parse your input'
			fi
		fi
	elif [ "${profile_path_option}" = 'all' ]; then
		for file in "${ff_file}" "${tb_file}"; do
			if [ -f "${file}" ]; then
				profile_path_temp="$(sed -ne 's/^Path=\(.*\)$/\1/p' "${file}")"
			else
				continue
			fi
			while read -r line; do
				if ! printf '%s' "${line}" | grep -q '^/'; then
					profile_path_to_update="${file%profiles.ini}${line}"
				else
					profile_path_to_update="${line}"
				fi
				update_profile_path "${profile_path_to_update}"
			# we don't want a subshell here
			done <<EOF
${profile_path_temp}
EOF
		done
	elif [ "${profile_path_option}" = 'false' ]; then
		possible_userjs_dir="$(dirname "${script_path}")"
		# user.js in current working directory?
		if [ -e 'user.js' ] || [ -e "${compat_file}" ] || [ -e 'times.json' ]; then
			update_profile_path "$(pwd)"
		# user.js in same directory as script?
		elif [ -e "${possible_userjs_dir}/user.js" ]; then
			update_profile_path "${possible_userjs_dir}"
		else
			err_exit "Could not detect user.js location. Use '-p PROFILE'"
		fi
	fi
	if [ -z "${profile_path}" ]; then
		err_exit 'No valid profile path found'
	fi
	readonly profile_path
}

# Download user.js and put it to given path. Note the variables that should be
# set for get_userjs_link(). Requires $profile_path_option to be set.
# 	$1: path where the user.js should be saved
# 	$2: set to 'true' to indicate a Firefox profile, otherwise no global
# 			overrides will be appended
get_userjs() {
	if [ ! -f "${1}" ]; then
		link="$(get_userjs_link)"
		if ! download_file "${1}" "${link}"; then
			if [ "${profile_path_option}" = 'all' ]; then
				print_warn 'Download of new user.js failed'
				return 1
			else
				err_exit 'Download of new user.js failed'
			fi
		fi
		[ "${2}" = 'true' ] && add_global_overrides "${1}"
	fi
	if ! new_version_long="$(get_userjs_version "${new_userjs}")"; then
		err_exit 'Could not parse new user.js'
	fi
	printv_info '0' '    new user.js'  "${new_version_long}"
}

# Open a file for the user to view.
# 	$1: path to file
open_file() {
	"${open_command}" "${1}" || err_exit 'Cannot open files on your OS'
}

# Parse profiles.ini to find the correct profile for the user.js.
# 	$1: absolute path of profiles.ini
read_ini_file() {
	readonly ini_file="${1}"
	# only 1 profile found
	if [ "$(grep -c '^\[Profile' "${ini_file}")" -eq 1 ]; then
		ini_content="$(grep '^\[Profile' -A 4 "${ini_file}")"
	# more than 1 profile
	else
		printf 'Profiles found:\n'
		printf '————————————————————————————————————————————\n'
		grep -E 'Default=[^1]|\[Profile[0-9]*\]|Name=|Path=|^$' "${ini_file}"
		printf '————————————————————————————————————————————\n'
		printf '%s' "${color_user_input}Select the profile number"
		printf '%s' " (Profile(0), Profile(1) etc.): 1,2,…?${no_color_stdout} "
		read -r reply
		printf '\n'
		ini_content="$(grep '^\[Profile'"${reply}" -A 4 "${ini_file}")" \
			|| err_exit "Profile${reply} does not exist"
	fi

	profile_path_to_add="$(printf '%s' "${ini_content}" \
		| sed -n 's/^Path=\(.*\)$/\1/p')"
	path_is_rel="$(printf '%s' "${ini_content}" \
		| sed -n 's/^IsRelative=\([01]\)$/\1/p')"
	# update global variable if path is relative
	[ "${path_is_rel}" -eq 1 ] \
		&& profile_path_to_add="$(dirname "${ini_file}")/${profile_path_to_add}"
	update_profile_path "${profile_path_to_add}"
}

# Add a path to the list of profile paths, separated by newlines.
# 	$1: one profile path
update_profile_path() {
	if [ ! -d "${1}" ]; then
		print_warn "Invalid profile path: '${1}'"
		return 1
	fi
	if [ -e "${1}/user.js" ] && [ ! -w "${1}/user.js" ]; then
		print_warn "No permission to write file: '${1}/user.js'"
		return 1
	fi
	# if profile path empty, set variable to path
	if [ -z "${profile_path}" ]; then
		profile_path="${1}"
	# else append profile path
	else
		profile_path="$(printf '%s\n%s' "${profile_path}" "${1}")"
	fi
}

#########################
#    Update Updater     #
#########################

# Update this updater. Call this function with "$@" as argument.
update_updater() {
	# update check has not been activated
	[ "${update}" = 'false' ] && return 0
	readonly new_updater="${tmp_dir}/new_updater"
	if ! download_file "${new_updater}" \
			'https://notabug.org/TotallyLeGIT/PH-userjs-updater/raw/main/updater'
	then
		print_warn 'Download of new updater failed'
		return 1
	fi
	chmod u+x "${new_updater}"
	current_updater_version="$("${script_path}" -v | cut -d ' ' -f 2)"
	new_updater_version="$("${new_updater}" -v | cut -d ' ' -f 2)"

	# compare updater versions, if different
	while [ "${current_updater_version}" != "${new_updater_version}" ]; do
		difference="$((${new_updater_version%%.*}-${current_updater_version%%.*}))"
		# newer version available
		if [ "${difference}" -gt 0 ]; then
			printv_info '0' 'updater' 'update available'
			# apply update
			if ! { cat "${new_updater}" > "${script_path}" ; } > /dev/null 2>&1; then
				print_warn "No permission to write file: '${script_path}'"
				return 1
			fi
			printv_info '1' 'changelog' \
					'https://notabug.org/TotallyLeGIT/PH-userjs-updater/raw/main/CHANGELOG.md'
			# update user.js with new version
			"${script_path}" "${@}"
			# exit with exit code from updater call above
			exit "${?}"
		# version number is equal (go to next number)
		elif [ "${difference}" -eq 0 ]; then
			current_updater_version="${current_updater_version#*.}"
			new_updater_version="${new_updater_version#*.}"
		# this should not be happening
		else
			print_warn 'Your updater version seems odd. Check updater homepage' \
					'and/or update manually.'
			return 2
		fi
	done
	printv_info '0' 'updater' 'up to date'
}

#########################
#   Update user.js      #
#########################

# Add global overrides to a user.js.
# 	$1: Path to a file which the overrides should be appended to
add_global_overrides() {
	[ ! -e "${global_override_path}" ] && return 0
	printf '\n\n// global user-overrides\n' >> "${1}"
	append_overrides_content "${global_override_path}" "${1}" '1'
}

# Add local overrides to a user.js. Acts on a user.js whose path must be
# stored in the variable $new_userjs which is changed if there are overrides.
add_profile_overrides() {
	{ [ "${override}" = 'false' ] || [ ! -e "${override_path}" ] ; } && return 0
	new_userjs_tmp="${new_userjs}_$(basename "${PWD}")"
	cp "${new_userjs}" "${new_userjs_tmp}"
	new_userjs="${new_userjs_tmp}"
	printf '\n\n// user-overrides\n' >> "${new_userjs}"
	append_overrides_content "${override_path}" "${new_userjs}"
}

# Append given override file or all files suffixed .js in given directory.
# 	$1: Path to overrides
# 	$2: Path to a file to append the overrides to
# 	$3: Set to anything different than empty or 0 to indicate global overrides
append_overrides_content() {
	input="${1}"
	output="${2}"

	if [ -f "${input}" ]; then
		printf '\n' >> "${output}"
		cat "${input}" >> "${output}"
		[ "${3:-0}" = 0 ] && printv_info '0' 'profile overrides' "${input}"
	elif [ -d "${input}" ]; then
		files="$(find "${input}" -mindepth 1 -maxdepth 1 -type f -name '*.js')"
		if [ -z "${files}" ]; then
			print_warn "No .js file in directory: '${input}'"
			return 1
		else
			printf '%s\n' "${files}" \
				| while read -r f
			do
				append_overrides_content "${f}" "${output}" "${3}"
			done
		fi
	else
		if [ "${override}" = 'soft' ]; then
			printv_info '0' 'profile overrides' 'none found'
		else
			print_warn "Invalid overrides path: '${input}'"
			return 1
		fi
	fi
}

# Perform checks whether the user.js to be downloaded seems sane given a
# specific profile (profile = current working dir) and warn if not.
check_userjs_against_profile() {
	unset print_warning
	[ -z "${version_profile_compat}" ] && return 0
	if [ "${ff_version_wanted_global}" != "${version_profile_compat}" ]
	then
		printv_info '0' 'profile used by' "${version_profile_compat}"
	fi
	# Thunderbird
	if [ -z "${is_config_wanted_profile_ff}" ]; then
		if [ "${version_profile_compat%% *}" = 'Firefox' ]; then
			print_warning='true'
		fi
	# Firefox
	else
		if [ "${version_profile_compat}" = 'Thunderbird' ]; then
			print_warning='true'
		# Firefox update channels do not match
		elif [ "${version_profile_compat#Firefox release}" != "$(printf \
				'%s' "${esr_to_dl}" | sed 's/ff/Firefox ESR /')" ]
		then
			print_warning='true'
		fi
	fi
}

# Return a user.js download link. Variables used here must be set beforehand.
get_userjs_link() {
	case "${config_wanted_profile}" in
		empty)
			printf 'empty'
			return ;;
		arkenfox)
			printf 'https://raw.githubusercontent.com/arkenfox/user.js/master/user.js'
			return ;;
		HorlogeSkynet|horlogeskynet)
			printf 'https://raw.githubusercontent.com/HorlogeSkynet/thunderbird-user.js/master/user.js'
			return ;;
	esac
	printf '%s' "https://www.privacy-handbuch.de/download/"
	if [ -n "${is_config_wanted_profile_ff}" ]; then
		printf '%s' "${esr_to_dl:+$esr_to_dl/}"
	fi
	printf '%s' "${config_wanted_profile}/user.js"
}

# Print the version of a PH user.js file or return 1 if no user.js present.
get_userjs_version() {
	[ -f "${1}" ] && userjs_version="$(grep -m1 \
		'_user\.js\.prhdb\|https://github\.com/\(arkenfox/\|HorlogeSkynet/thunderbird-\)user\.js' "${1}" \
		| sed -ne '/arkenfox/ s/.*/arkenfox/p' \
					-e '/HorlogeSkynet/ s/.*/HorlogeSkynet/p' \
					-e '/prhdb/ s/^[^"]*"[^"]*"[^"]*"\([^"]*\)"[^"]*$/\1/p')"
	[ -z "${userjs_version}" ] && return 1
	printf '%s' "${userjs_version}"
}

# Set $version_profile_compat to Thunderbird/Firefox version. Executable path
# is taken from $compat_file. Current working directory needs to be the profile.
#
# Return: 1 if $compat_file doesn't exist or on error, else 0.
get_version_compat() {
	if [ -e "${compat_file}" ]; then
		exec_dir_profile="$(sed \
				-ne 's/^LastPlatformDir=\([[:graph:]]*\)$/\1/p' "${compat_file}" \
			| sed -e "${sed_compat_os_script}")"
		if version_profile_compat="$(extract_version "${exec_dir_profile}/")"; then
			printf '%s' "${version_profile_compat}"
			return 0
		fi
	fi
	return 1
}

# Download a user.js, open it and exit.
read_and_exit() {
	[ "${just_read}" = 'false' ] && return 0
	if ! set_config_wanted_profile; then
		err_exit "Could not determine which CONFIG to download. Use '-c CONFIG'"
	fi
	set_esr_to_dl
	userjs_tmp="$(mktemp -dt userjs.XXXXXX)/${esr_to_dl}${config_wanted_profile}"
	if ! download_file "${userjs_tmp}" "$(get_userjs_link)"; then
		err_exit 'Download of new user.js failed'
	fi
	printv_info '0' 'user.js saved to' "${userjs_tmp}"
	open_file "${userjs_tmp}"
	exit 0
}

# Set $config_wanted_profile to the CONFIG the user wants for this profile.
set_config_wanted_profile() {
	unset is_config_wanted_profile_ff
	# 'keep' means to try autodetection
	if [ "${config_wanted_global}" = 'keep' ]; then
		# config detection impossible
		[ -z "${current_userjs_version_long}" ] && return 1
		config_wanted_profile="${current_userjs_version_long%_*}"
		# will be e. g. 'streng' or empty afterwards
		config_wanted_profile="${config_wanted_profile#*_}"
		# TODO: remove %2 in future version (tb2 leftover)
		config_wanted_profile="${config_wanted_profile%2}"
	else
		config_wanted_profile="${config_wanted_global}"
	fi
	if [ "${config_wanted_profile%-streng}" != 'tb' ]; then
		is_config_wanted_profile_ff='true'
	fi
}

# Set $esr_to_dl to something like 'ff91' or empty
set_esr_to_dl() {
	# don't waste time if already globally forced by user
	[ "${ff_version_specified}" = 'true' ] && return 0
	unset esr_to_dl
	[ -z "${is_config_wanted_profile_ff}" ] && return 0
	if [ -n "${current_userjs_version_long}" ] \
			&& [ -n "${current_userjs_version_long%%tb*}" ]
	then
		esr_to_dl="$(printf '%s' "${current_userjs_version_long}" \
			| sed -ne 's/\(ff[[:digit:]]*\)_.*/\1/p')"
	elif [ "${version_profile_compat%% *}" = 'Firefox' ]; then
		if [ "${version_profile_compat% *}" = 'Firefox ESR' ]; then
			esr_to_dl="ff${version_profile_compat##* }"
		fi
	elif [ "${ff_version_wanted_global%% *}" = 'Firefox' ]; then
		if [ "${ff_version_wanted_global% *}" = 'Firefox ESR' ]; then
			esr_to_dl="ff${ff_version_wanted_global##* }"
		fi
	# Could not find any update channel
	else
		err_exit "Could not determine Firefox update channel. Use '-e VERSION'"
	fi
}

# Apply chosen version of user.js and any custom overrides.
update_userjs() {
	current_userjs_version_long="$(get_userjs_version 'user.js')"
	printv_info '1' 'profile path' "${PWD}"
	if ! set_config_wanted_profile; then
		if [ "${profile_path_option}" = 'all' ]; then
			printv_info '1' 'user.js status' 'none found – skipped'
			return 1
		else
			err_exit 'Could not determine which CONFIG to download. Use' \
					"'-c CONFIG' and/or '-p PROFILE'"
		fi
	fi
	version_profile_compat="$(get_version_compat)"
	set_esr_to_dl
	check_userjs_against_profile
	printv_info '0' 'current user.js' "${current_userjs_version_long:-not detected}"
	new_userjs="${tmp_dir}/userjs_${is_config_wanted_profile_ff:+$esr_to_dl}${config_wanted_profile}"
	get_userjs "${new_userjs}" "${is_config_wanted_profile_ff}" || return 1
	# if `check_userjs_against_profile` found something
	if [ -n "${print_warning}" ]; then
		print_warn "It seems the user.js won't fit this profile. Please" \
				'recheck your configuration.'
	fi
	add_profile_overrides
	# Inspect (-i). If the user.js is identical, this script exits and deletes
	# the temporary user.js faster than open_file calling xdg-open opens it, so
	# it opens an already deleted, thus empty file. Sleep works around this.
	# It shouldn't be too bad, as the editor is opened in foreground anyway and
	# -i is probably not used a lot – not in a fast update run anyway.
	[ "${view}" = 'true' ] && open_file "${new_userjs}" && sleep 0.5
	# files are identical
	if cmp -s "${new_userjs}" 'user.js'; then
		printv_info '1' 'user.js status' 'identical – skipped'
		return 0
	fi
	if [ "${confirm}" = 'yes' ]; then
		printf '\n%s' "${color_user_input}Update user.js?"
		printf '\n%s' "(y)es / (n)o: Y,n?${no_color_stdout} "
		IFS='' read -r reply </dev/tty
		# y, j or empty → comfirmation
		if printf '%s' "${reply}" | grep -vq '^[yYjJ]*$'; then
			printf '%s\n' "${color_info}skipping profile${no_color_stdout}"
			return 1
		fi
	fi
	if [ -f 'user.js' ]; then
		status_output='backed up and updated'
	else
		status_output='installed'
	fi
	if [ "${dry_run}" = 'false' ]; then
		# create diff
		[ "${backup_multiple}" = 'true' ] && date_file="$(date '+%FT%T')_" \
				|| date_file=''
		if [ "${compare}" = 'true' ]; then
			mkdir -p "${diff_dir}"
			diff -wBU 0 'user.js' "${new_userjs}" > "${diff_dir}/${date_file}diff.txt"
			printv_info '0' 'diff status' 'diff file created'
		fi
		# backup user.js
		mkdir -p "${backup_dir}"
		backup_file="${backup_dir}/${date_file}user.js"
		[ -f 'user.js' ] && cp -p 'user.js' "${backup_file}"
		# install newest user.js
		cat "${new_userjs}" > 'user.js'
		dry_run_output=''
	else
		dry_run_output=' (DRY-RUN)'
	fi
	printv_info '1' 'user.js status' "${status_output}${dry_run_output}"
	return 0
}

#########################
#        Main()         #
#########################

set_variables
parse_options "${@}"
prepare "${0}"
update_updater "${@}"
abstract_os
set_ff_version_wanted_global
read_and_exit
print_global_overrides
get_profile_path
while read -r line; do
	printv '0' '————————————————————————————————————————————\n'
	# this enables us to leave relative profile paths as they are
	{ cd "${pwd_start}" && cd "${line}" ; } || exit 99
	update_userjs
done <<EOF
${profile_path}
EOF

# vim: shiftwidth=2 tabstop=2 noexpandtab
